﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using PC.PickleJar.Attributes;
using PC.PickleJar.Configuration;
using PC.PickleJar.Helpers;
using PC.PickleJar.MSTest.Helpers;
using PC.PickleJar.Validators;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PC.PickleJar
{
	public sealed class PickleJarTestMethodInvoker : ITestMethodInvoker
	{
		private TestMethodInvokerContext context;

		public PickleJarTestMethodInvoker(TestMethodInvokerContext context)
		{
			this.context = context;
		}

		public TestMethodInvokerResult Invoke(params object[] parameters)
		{
			TestMethodInvokerResult result = null;

			PickleJarEngine engine = new PickleJarEngine();
			var engineResult = engine.ValidateTestMethod(this.context.TestMethodInfo);

			if (engineResult.Result == ResultEnum.Success)
			{
				var customeInvokerType = PickleJarTestMethodInvoker.ResolveCustomInvokerType(new MSTestConfigurationHelper(), context);
				var customInvoker = PickleJarTestMethodInvoker.ResolveCustomInvoker(customeInvokerType, this.context);

				var invoker = customInvoker ?? this.context.InnerInvoker;
				result = invoker.Invoke(parameters);
			}
			else
			{
				result = new TestMethodInvokerResult();
				result.Exception = PickleJarTestMethodInvoker.GetAppropriateException(engineResult.Message, engineResult.Action);
			}

			return result;
		}

		private static Exception GetAppropriateException(String message, ResultActionEnum action)
		{
			Exception result;

			switch (action)
			{
				case ResultActionEnum.SkipTest:
					result = new AssertInconclusiveException(message);
					break;

				case ResultActionEnum.FailTest:
					result = new AssertFailedException(message);
					break;

				default:
					throw new InvalidOperationException("Unexpected ResultAction");
			}

			return result;
		}

		private static ITestMethodInvoker ResolveCustomInvoker(Type invokerType, TestMethodInvokerContext context)
		{
			if (invokerType == null)
			{
				return null;
			}

			ITestMethodInvoker result = null;

			var parameterlessContructor = invokerType.GetConstructor(Type.EmptyTypes);
			var constructorWithContextParameter = invokerType.GetConstructor(new Type[] { typeof(TestMethodInvokerContext) });

			if (constructorWithContextParameter == null && parameterlessContructor == null)
			{
				throw new InvalidOperationException(
					String.Format(
						CultureInfo.InvariantCulture,
						"Could not find compatible constructor for ITestMethodInvoker {0}.{1}Please provide either a parameterless constructor, or a constructor with a single TestMethodInvokerContext parameter.",
						invokerType.Name,
						Environment.NewLine));
			}

			if (constructorWithContextParameter != null)
			{
				result = (ITestMethodInvoker)constructorWithContextParameter.Invoke(new object[] { context });
			}
			else
			{
				if (parameterlessContructor != null)
				{
					result = (ITestMethodInvoker)parameterlessContructor.Invoke(null);
				}
			}

			return result;
		}

		private static Type ResolveCustomInvokerType(MSTestConfigurationHelper config, TestMethodInvokerContext context)
		{
			var pickleJarTestAttribute = context.TestMethodInfo.DeclaringType.GetCustomAttributes(typeof(PickleJarTestAttribute), false).Cast<PickleJarTestAttribute>().Single();
			Type customInvokerType = null;
			
			var customInvokerTypeName = pickleJarTestAttribute.CustomTestMethodInvokerTypeName;

			if (String.IsNullOrWhiteSpace(customInvokerTypeName))
			{
				customInvokerTypeName = config.CustomTestMethodInvokerTypeName;
			}

			if (!String.IsNullOrWhiteSpace(customInvokerTypeName))
			{
				customInvokerType = Type.GetType(customInvokerTypeName);
			}

			return customInvokerType;
		}
	}
}
